const { defaultsDeep } = require("lodash");
const path = require("path");
const logError = require("./logError");
const logInfo = require("./logInfo");
const logStep = require("./logStep");
const defaultOptions = require("./options");
const PluginAPI = require("./PluginAPI");

module.exports = class Service {
  constructor(context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
    logStep(
      "Service constructor 主要执行了 pkg 的获取和 plugins 的设置。 特别 plugins 是 Service 的灵魂所在"
    );
    process.VUE_CLI_SERVICE = this;
    this.initialized = false;
    this.context = context;
    this.inlineOptions = inlineOptions;
    this.webpackChainFns = [];
    this.webpackRawCOnfigFns = [];
    this.devServerConfigFns = [];
    this.commands = {};

    this.pkgContext = context;
    this.pkg = this.realovePkg(pkg);

    this.plugins = this.reslovePlugins(plugins, useBuiltIn);

    this.pluginsToSkip = new Set();
    logStep(
      "获取配置的不同命令下的mode，正常开发中需要从 plugin 中获取 module.exports.defaultModes。"
    );
    this.modes = {
      build: "procedure",
      serve: "development",
    };
  }

  async run(name, args = {}, rawArgv = {}) {
    logStep("根据命令获取 mode， 如果是 build+watch时 固定为 development");
    const mode =
      args.mode ||
      (name === "build" && args.watch ? "development" : this.modes[name]);
    logInfo(
      "有些插件时再 init 时执行，有些是再 init 之后执行的。这时就需要配置在 skips 中"
    );
    this.init(mode);

    args._ = args._ || [];
    let command = this.commands[name];
    if (!command && name) {
      logError("不存在的命令");
      process.exit(1);
    }
    logStep("取到命令后开始执行命令：" + name);
    const { fn } = command;
    return fn(args, rawArgv);
  }

  init(mode = process.env.VUE_CLI_MODE) {
    if (this.initialized) {
      //懒加载
      return;
    }
    this.initialized = true;
    this.mode = mode;
    if (mode) {
      this.loadEnv(mode);
    }
    const userOptions = this.loadUserOptions();
    this.projectOptions = defaultsDeep(userOptions, defaultOptions);

    logStep(
      "这里开始调用所有插件的 apply 方法安装插件，这里和 @vue/cli 里的 Generator+GeneratorAPI 的设计思想一致"
    );
    logStep("这里也是真正的执行命令开始调用的地方。");
    console.log(this.plugins);
    this.plugins.forEach(({ id, apply }) => {
      if (this.pluginsToSkip.has(id)) return;
      apply(new PluginAPI(id, this), this.projectOptions);
    });

    logInfo(
      "安装插件后会向 this.commands 中注册命令，只有这里注册过了，后边才可以使用。"
    );
    logStep("webpack 命令的合并");
    logInfo(
      "this.webpackChainFns.push(this.projectOptions.chainWebpack)  + this.webpackChainFns.push(this.projectOptions.configureWebpack) "
    );
    if (this.projectOptions.chainWebpack) {
      this.webpackChainFns.push(this.projectOptions.chainWebpack);
    }
    if (this.projectOptions.configureWebpack) {
      this.webpackChainFns.push(this.projectOptions.configureWebpack);
    }
  }

  loadEnv(mode) {
    logStep(`利用 dotenv-expand 将 [mode:${mode}] 加载到环境变量中`);
  }

  loadUserOptions() {
    logStep("从 vue.config.js 中获取用户配置。");
    return {};
  }

  realovePkg(inlinePkg, context = this.context) {
    logStep("解析 package 文件内容");
    return require(path.resolve(context, "package.json"));
  }

  reslovePlugins(inlinePlugins, useBuilltIn) {
    logStep(
      "解析拼接内部插件，以及是否使用 useBuiltIn 等其他插件,这里是 vue-cli-service 插件系统的核心入口"
    );
    logInfo("这里也说明了 service 内部也采用了 plugin 的模式来实现功能的堆叠");
    logInfo("例如：./commands/serve ./command/build ./commands/inspect");

    const idToPlugin = (id) => ({
      id: id.replace(/^.\//, "built-in:"),
      apply: require(id),
    });

    const buildInPlugins = [
      //   "./commands/serve",
      "./commands/build",
      //   "./commands/inspect",
      //   "./commands/help",
    ].map(idToPlugin);

    if (inlinePlugins) {
      logStep(`加载其他默认的插件 ${inlinePlugins}`);
      return buildInPlugins.concat(inlinePlugins);
    } else {
      logStep(
        `加载 package 中配置的插件：${
          this.pkg.devDependencies
        },${JSON.stringify(this.pkg.dependencies)}`
      );
      logInfo(
        "这里就是为什么cli-service 可以加载pkg 中的插件，而无需额外配置的原因。"
      );
      logInfo(
        "是否是cli-service的插件依靠命名来确定：/^(@vue/|vue-|@[w-]+/vue-)cli-plugin-/"
      );
      const projectPlugins = Object.keys(this.pkg.devDependencies || {})
        .concat(Object.keys(this.pkg.dependencies || {}))
        .filter((id) => {
          return /^(@vue\/|vue-|@[\w-]+\/vue-)cli-plugin-/.test(id);
        })
        .map((id) => {
          let apply = () => {};
          try {
            apply = require(id);
          } catch (error) {}
          return { id, apply };
        });
      logInfo(projectPlugins);
      return buildInPlugins.concat(projectPlugins);
    }
  }
};
